package com.czk.plugins;

import com.czk.anotation.TableShared;
import com.czk.interfaces.ITableSharedStrategy;
import com.czk.utils.ApplicationHolder;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Map;

/**
 * 分库的拦截器
 * 可以和分页拦截器写在一起
 *
 * @Author:ChenZhangKun
 * @Date: 2021/5/17 10:10
 */
@Intercepts({
        @Signature(
                type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class}
        )
})
@Component
@Order(4)
public class TableShardInterceptor implements Interceptor {
    private static final ReflectorFactory defaultReflectFactory = new DefaultReflectorFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 拿到反射工具
        MetaObject metaObject = getMetaObject(invocation);
        // 拿到boundSql对象
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        // 拿到mappedStatement对象
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // 获取mapper执行方法
        Method method = invocation.getMethod();
        // 获取分表注解
        TableShared tableShared = getTableShared(method, mappedStatement);
        // 是否分表
        if (tableShared == null) {
            // 放行
            invocation.proceed();
        }
        // 拿到值
        String value = tableShared.value();
        // n拿到是否依赖字段名
        boolean flag = tableShared.fieldFlag();
        if (flag) {
            // 获取请求参数
            Object parameterObject = boundSql.getParameterObject();
            //
            if (parameterObject instanceof MapperMethod.ParamMap) {
                // 强转
                MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameterObject;
                // 根据字段名称获取参数值
                Object o = paramMap.get(value);
                if (o == null) {
                    throw new RuntimeException(String.format("入参字段%s无匹配", value));
                }
                // 替换sql
                replaceSql(tableShared, o, metaObject, boundSql);
            } else {
                // 单参数逻辑
                if (isBaseType(parameterObject)) {
                    throw new RuntimeException("单参数非法，请使用param注解");
                }
                if (parameterObject instanceof Map) {
                    Map<String, Object> map = (Map<String, Object>) parameterObject;
                    Object o = map.get(value);
                    replaceSql(tableShared, o, metaObject, boundSql);
                } else {
                    // 非基础类型对象
                    Class<?> clazz = parameterObject.getClass();
                    Field field = clazz.getDeclaredField(value);
                    field.setAccessible(true);
                    Object o = field.get(parameterObject);
                    replaceSql(tableShared, o, metaObject, boundSql);
                }
            }
        } else {
            replaceSql(tableShared, value, metaObject, boundSql);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身, 减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    /**
     * 是否是基本类型判断
     *
     * @param parameterObject
     * @return
     */
    private boolean isBaseType(Object parameterObject) {
        if (parameterObject.getClass().isPrimitive()
                || parameterObject instanceof String
                || parameterObject instanceof Integer
                || parameterObject instanceof Long
                || parameterObject instanceof Short
                || parameterObject instanceof Boolean
                || parameterObject instanceof Float
                || parameterObject instanceof Double
                || parameterObject instanceof Byte
        ) {
            return true;
        } else {
            return false;
        }

    }

    /**
     * 替换sql逻辑
     *
     * @param tableShared 分表注解
     * @param o           value
     * @param metaObject  元数据
     * @param boundSql    绑定的sql
     */
    private void replaceSql(TableShared tableShared, Object o, MetaObject metaObject, BoundSql boundSql) {
        // 拿到前缀
        String tablePrefix = tableShared.tablePrefix();
        // 获取策略
        Class<? extends ITableSharedStrategy> clazz = tableShared.sharedStrategy();
        // 获取策略类
        ITableSharedStrategy iTableSharedStrategy = (ITableSharedStrategy) ApplicationHolder.getObject(clazz);
        // 生成表名
        String tableName = iTableSharedStrategy.generateTableName(tablePrefix, o);
        // 获取sql
        String sql = boundSql.getSql();
        // 完成表名替换
        metaObject.setValue("delegate.boundSql.sql", sql.replaceAll(tablePrefix, tableName));
    }

    /**
     * 获取分表注解
     *
     * @param method
     * @param mappedStatement
     * @return
     * @throws ClassNotFoundException
     */
    private TableShared getTableShared(Method method, MappedStatement mappedStatement) throws ClassNotFoundException {
        String id = mappedStatement.getId();
        // 获取class
        String className = id.substring(0, id.lastIndexOf("."));
        // 拿到分表注解
        TableShared tableShared = null;
        tableShared = method.getAnnotation(TableShared.class);
        //s 是否为空
        if (tableShared == null) {
            // 从类上获取
            tableShared = Class.forName(className).getAnnotation(TableShared.class);
        }
        return tableShared;
    }

    /**
     * 返回元数据信息
     *
     * @param invocation
     * @return
     */
    private MetaObject getMetaObject(Invocation invocation) {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = MetaObject.forObject(statementHandler,
                SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                defaultReflectFactory);
        return metaObject;
    }
}
